﻿using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Xml;
using EnvDTE;
using EnvDTE80;
using Microsoft.TeamFoundation.Client;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.CommandBars;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TeamFoundation;
using Microsoft.VisualStudio.TeamFoundation.WorkItemTracking;

namespace VsExt_SearchWorkItems
{
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guid_SearchWorkItemsPkgString)]
    [ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids80.NoSolution)]
    public sealed class SearchWorkItemsPackage : Package, IVsShellPropertyEvents
    {
        private const string SearchToken = "[search]";
        private const string TeamFoundationServerExtService = "Microsoft.VisualStudio.TeamFoundation.TeamFoundationServerExt";
        private const string WiqlXPath = "/WorkItemQuery/Wiql";
        private const string WorkItemTrackingDocumentService = "Microsoft.VisualStudio.TeamFoundation.WorkItemTracking.DocumentService";
        private uint _EventSinkCookie;
        private string currentMRUComboChoice = null;
        private DTE2 dte2;

        public SearchWorkItemsPackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }

        private string TemplateFilePath
        {
            get
            {
                return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "SearchTemplate.wiq");
            }
        }

        public int OnShellPropertyChange(int propid, object propValue)
        {
            // --- We handle the event if zombie state changes from true to false
            if ((int)__VSSPROPID.VSSPROPID_Zombie == propid)
            {
                if ((bool)propValue == false)
                {
                    // --- Show the commandbar
                    var cbs = ((CommandBars)dte2.CommandBars);
                    CommandBar cb = cbs["Search Work Items"];
                    cb.Visible = true;

                    // --- Unsubscribe from events
                    var shellService = GetService(typeof(SVsShell)) as IVsShell;
                    if (shellService != null)
                    {
                        ErrorHandler.ThrowOnFailure(shellService.
                                                    UnadviseShellPropertyChanges(_EventSinkCookie));
                    }

                    _EventSinkCookie = 0;
                }
            }

            return VSConstants.S_OK;
        }

        protected override void Initialize()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            dte2 = (DTE2)GetService(typeof(DTE));

            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (null != mcs)
            {
                CommandID menuMyMRUComboCommandID = new CommandID(GuidList.guid_SearchWorkItemsCmdSet, (int)PkgCmdIDList.cmdidSearchWorkItemsCombo);
                OleMenuCommand menuMyMRUComboCommand = new OleMenuCommand(new EventHandler(OnMenuMyMRUCombo), menuMyMRUComboCommandID);
                menuMyMRUComboCommand.ParametersDescription = "$"; // accept any argument string
                mcs.AddCommand(menuMyMRUComboCommand);
            }

            // -- Set an event listener for shell property changes

            var shellService = GetService(typeof(SVsShell)) as IVsShell;
            if (shellService != null)
            {
                ErrorHandler.ThrowOnFailure(shellService.
                                            AdviseShellPropertyChanges(this, out _EventSinkCookie));
            }
        }

        private TeamFoundationServerExt GetTFSExt()
        {
            // Get access to the TFS objects
            TeamFoundationServerExt tfsExt = this.dte2.GetObject(TeamFoundationServerExtService) as TeamFoundationServerExt;

            // Make sure the user is connected to a Team Foundation Server
            if (tfsExt.ActiveProjectContext.DomainUri == null || tfsExt.ActiveProjectContext.ProjectUri == null)
            {
                MessageBox.Show(Resources.ErrorNotConnected, Resources.ToolbarName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                return null;
            }
            else
            {
                return tfsExt;
            }
        }

        private void LoadSearchTemplate()
        {
            // Make sure there is an active connection to a TFS server
            TeamFoundationServerExt tfsExt = this.GetTFSExt();
            if (tfsExt == null)
            {
                return;
            }

            // Modify the template for the current active context
            XmlDocument doc = new XmlDocument();
            doc.Load(this.TemplateFilePath);
            doc.DocumentElement["TeamFoundationServer"].InnerText = tfsExt.ActiveProjectContext.DomainUri;
            doc.DocumentElement["TeamProject"].InnerText = tfsExt.ActiveProjectContext.ProjectName;
            doc.Save(this.TemplateFilePath);

            // Show the template being used
            System.Diagnostics.Process.Start(this.TemplateFilePath);
        }

        private void OnMenuMyMRUCombo(object sender, EventArgs e)
        {
            if (e == EventArgs.Empty)
            {
                // We should never get here; EventArgs are required.
                throw (new ArgumentException(Resources.EventArgsRequired)); // force an exception to be thrown
            }

            OleMenuCmdEventArgs eventArgs = e as OleMenuCmdEventArgs;

            if (eventArgs != null)
            {
                object input = eventArgs.InValue;
                IntPtr vOut = eventArgs.OutValue;

                if (vOut != IntPtr.Zero && input != null)
                {
                    throw (new ArgumentException(Resources.BothInOutParamsIllegal)); // force an exception to be thrown
                }
                else if (vOut != IntPtr.Zero)
                {
                    // when vOut is non-NULL, the IDE is requesting the current value for the combo
                    Marshal.GetNativeVariantForObject(this.currentMRUComboChoice, vOut);
                }
                else if (input != null)
                {
                    string newChoice = input.ToString();

                    // new value was selected or typed in
                    if (!string.IsNullOrEmpty(newChoice))
                    {
                        this.currentMRUComboChoice = newChoice;
                        RunSearchCommand(this.currentMRUComboChoice);
                    }
                    else
                    {
                        // We should never get here
                        throw (new ArgumentException(Resources.EmptyStringIllegal)); // force an exception to be thrown
                    }
                }
                else
                {
                    throw (new ArgumentException(Resources.BothInOutParamsIllegal)); // force an exception to be thrown
                }
            }
            else
            {
                // We should never get here; EventArgs are required.
                throw (new ArgumentException(Resources.EventArgsRequired)); // force an exception to be thrown
            }
        }

        private void RunSearchCommand(string input)
        {
            // Normalize the user's input
            string check = input.ToLowerInvariant().Trim();

            // Execute various commands
            if (check == "--template")
            {
                this.LoadSearchTemplate();
            }
            else
            {
                this.SearchWorkItems(input);
            }
        }

        private void SearchWorkItems(string criteria)
        {
            TeamFoundationServerExt tfsExt = this.GetTFSExt();
            if (tfsExt == null)
            {
                return;
            }

            TfsTeamProjectCollection activeTFS = new TfsTeamProjectCollection(new Uri(tfsExt.ActiveProjectContext.DomainUri));

            // Access the work item store
            DocumentService witDocumentService;
            witDocumentService = (DocumentService)dte2.DTE.GetObject(WorkItemTrackingDocumentService);

            // Determine if the user is searching for a work item number.
            // We do this BEFORE normalizing the criteria because if the
            // user enters a quoted integer (which fails int.TryParse then
            // we want to treat it as a normal search. If we do this test
            // AFTER the normalization then the quotes will have been
            // removed and int.TryParse will pass and it will be treated as
            // a work item number search.
            int wiid;
            bool searchForWorkItemNumber = int.TryParse(criteria, out wiid);

            // Normalize the criteria 
            criteria = criteria.Replace("'", "''").Replace("\"", "").Trim();

            // If the user entered an ID that exists, jump to the specific work item #
            if (searchForWorkItemNumber)
            {
                IWorkItemDocument widoc = null;
                widoc = witDocumentService.GetWorkItem(activeTFS, wiid, this);

                try
                {
                    witDocumentService.ShowWorkItem(widoc);
                }
                finally
                {
                    widoc.Release(this);
                }
            }
            else
            {
                // Load the WIQL template
                XmlDocument doc = new XmlDocument();
                doc.Load(this.TemplateFilePath);
                string wiql = doc.SelectSingleNode(WiqlXPath).InnerText.Replace(SearchToken, criteria);

                // Create the query
                IQueryDocument query = witDocumentService.CreateQuery(activeTFS, tfsExt.ActiveProjectContext.ProjectName, false, wiql, this);
                query.Name = String.Format(Resources.QueryName, criteria);

                // Run and display the results of the query
                IResultsDocument results = witDocumentService.GetLinkResults(query, this);

                try
                {
                    witDocumentService.ShowResults(results);
                }
                finally
                {
                    query.Release(this);
                    results.Release(this);
                }
            }
        }
    }
}

